#include "PageCache.h"
extern ObjectPool<Span> pool_for_spanlist;

PageCache PageCache::_sIns;//类作用域自身 

PageCache* PageCache::GetInstance()
{
    return &_sIns;
}
std::mutex& PageCache::GetMutex()
{
    return _mtx;
}

PageCache::PageCache()
{
    //对管理的所有SpanList对象延迟初始化
    for(size_t i = 1;i < NPAGES;i++)
    {
        _spanLists[i].DelayInitialize(pool_for_spanlist);
    }
}

// 获取一个npages页的span  1~128:哈希桶映射,分别表示页数,一页为8kb
Span* PageCache::NewSpan(size_t npages)
{
    assert(npages > 0);
    // assert(npages < NPAGES);
    if(npages >= NPAGES)
    {
        Span* span = pool_for_spanlist.New();
        void* ptr = SystemAlloc(npages);
        span->_isUse = true;
        span->_n = npages;
        span->_pageId = (PAGE_TYPE)ptr >> PAGE_SHIFT;
        span->_alignSize = npages << PAGE_SHIFT;
        _idSpanMap.set(span->_pageId,span);
        return span;
    }
    
    //先检查相应的第npages个桶里有没有span
    if(!_spanLists[npages].Empty())
    {
        Span* span = _spanLists[npages].PopFront();
        for(size_t i = 0;i < span->_n;i++)
        {
            // _idSpanMap[span->_pageId + i] = span;
            // 一个Span中可能有多页,每一页都要对应到相应Span
            _idSpanMap.set(span->_pageId + i,(void*)span);
        }
        span->_isUse = true;
        span->_alignSize = span->_n << PAGE_SHIFT;//记录空间对齐大小
    }

    //直接对应的相应的哈希桶为空,说明没有可以直接分配的空间,需要进行切分
    //将一个大于npages映射的哈希桶下的span,切成一个npages的span和一个k-npages的span
    for(size_t n = npages + 1;n < NPAGES;n++)
    {
        //不为空
        if(!_spanLists[n].Empty())
        {
            //拿到一个span
            Span* nSpan = _spanLists[n].PopFront();
            // Span* kSpan = new Span;
            Span* kSpan = pool_for_spanlist.New();
            //如何切?从头部切
            //从nSpan头部切下npages大小,kspan返回,nspan重新插入
            kSpan->_pageId = nSpan->_pageId;//这个id到底表示什么：见下面的解释--因此可以pageId << PAGE_SHIFT 来得到相应虚拟地址
            kSpan->_n = npages;
            kSpan->_alignSize = kSpan->_n << PAGE_SHIFT;

            nSpan->_pageId += npages;
            nSpan->_n -= npages;
            nSpan->_isUse = false;
            nSpan->_alignSize = nSpan->_n << PAGE_SHIFT;

            _spanLists[nSpan->_n].PushFront(nSpan);//注意这里使用_n,表示有几个8KB空间:这是用于page_cache哈希桶中的映射
            
            //对于nSpan:只需要映射首尾页号,因为此时不再是ThreadCache中归还被切分过的细碎内存空间(需要定位到相应span),而是相对整块内存的拼接(完整的页)
            // _idSpanMap[nSpan->_pageId] = nSpan;
            _idSpanMap.set(nSpan->_pageId,nSpan);
            // _idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;//注意此处的减去1
            _idSpanMap.set(nSpan->_pageId + nSpan->_n - 1,nSpan);


            //将返回给CentralCache的kSpan的相应PageId(以8kb为单位和对齐),记录到哈希表中,方便归还:需要记录每一个页
            for(size_t i = 0;i < kSpan->_n;i++)
            {
                // _idSpanMap[kSpan->_pageId + i] = kSpan;
                _idSpanMap.set(kSpan->_pageId + i,kSpan);
            }
            kSpan->_isUse = true;
            return kSpan;         
        }
    }

    //走到这里,就说明不存在更大页的Span,每个哈希桶都为空:最开始就是这样的情况
    //这时候就找堆要一个128页的span

    Span* bigSpan = pool_for_spanlist.New();
    void* ptr = SystemAlloc(NPAGES - 1);
    assert(ptr);
    bigSpan->_pageId = (PAGE_TYPE)ptr >> PAGE_SHIFT;//将这个虚拟地址内存空间按8KB的页进行划分,然后进行编号:编号从0开始
    bigSpan->_n = NPAGES - 1;
    bigSpan->_alignSize = bigSpan->_n << PAGE_SHIFT;
    // std::cout << "当前拿到的页起始地址为:" << ((PAGE_TYPE)ptr) << std::endl;
    _spanLists[bigSpan->_n].PushFront(bigSpan);
    
    return NewSpan(npages);//再次复用,因为此时肯定存在非空哈希桶了
}

//通过页号找相应Span
Span* PageCache::MapObjectToSpan(void* obj)
{
    PAGE_TYPE id = ((PAGE_TYPE)obj >> PAGE_SHIFT);
    // std::lock_guard<std::mutex> lg(_mtx);//stl中的容器并非是线程安全的

    Span* ptr = (Span*)_idSpanMap.get(id);
    if(ptr == nullptr)
        assert(false);//这时说明存在问题
    return ptr;
}

void PageCache::ReleaseToPage(Span* span)
{
    assert(span);
    //非PageCache情况,直接释放
    if(span->_n >= NPAGES)
    {
        void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
        SystemFree(ptr,span->_alignSize);
        pool_for_spanlist.Delete(span);
        return;
    }
    //通过_n成员可以准确定位到是PageCache的哪个页中
    //但是不能单纯进行归还,要合适地进行合并:解决内存碎片问题(外碎片问题)
    
    //如何合并:必须合并的是前后页!!!
    
    //合并前页:循环合并逻辑

    //下面循环一定不是死循环
    while(1)
    {
        PAGE_TYPE prevId = span->_pageId - 1;//不需要考虑从0开始的情况,进程地址空间的分布
        // auto ret = _idSpanMap.get(prevId);
        // if(ret == _idSpanMap.end())
        //     break;
        // else if(ret->second->_isUse == true)
        //     break;//前面的相邻页在被使用
        Span* ret = (Span*)_idSpanMap.get(prevId);
        if(ret == nullptr)
            break;
        else if(ret->_isUse == true)
            break;
        else
        {
            Span* prevSpan = ret;
            if(prevSpan->_n + span->_n > NPAGES - 1)
                break;//不合并
            //将prevSpan合并到span中
            span->_pageId = prevSpan->_pageId;
            span->_n += prevSpan->_n;
            
            // _idSpanMap[prevSpan->_pageId] = span;//保留头部
            _idSpanMap.set(prevSpan->_pageId,span);
            // _idSpanMap.erase(prevSpan->_pageId + prevSpan->_n - 1);//清除尾部
            _idSpanMap.set(prevSpan->_pageId + prevSpan->_n - 1,nullptr);
            _spanLists[prevSpan->_n].Erase(prevSpan);//从page_cache的页空间哈希结构所挂链表中清除
            // delete prevSpan;
            pool_for_spanlist.Delete(prevSpan);
        }
    }

    //向后合并
    while(1)
    {
        PAGE_TYPE nextId = span->_pageId + span->_n;
        // auto ret = _idSpanMap.find(nextId);
        // if(ret == _idSpanMap.end())
        //     break;
        // else if(ret->second->_isUse == true)
        //     break;//后面的相邻页在被使用
        Span* ret = (Span*)_idSpanMap.get(nextId);
        if(ret == nullptr) 
            break;
        else if(ret->_isUse == true)
            break;
        else
        {
            Span* nextSpan = ret;
            if(nextSpan->_n + span->_n > NPAGES - 1)//128
                break;//不合并
            //将prevSpan合并到span中
            span->_n += nextSpan->_n;
            
            // _idSpanMap[nextSpan->_pageId + nextSpan->_n - 1] = span;//保留尾部
            _idSpanMap.set(nextSpan->_pageId + nextSpan->_n - 1,span);
            // _idSpanMap.erase(nextSpan->_pageId);//清除头部
            _idSpanMap.set(nextSpan->_pageId,nullptr);
            _spanLists[nextSpan->_n].Erase(nextSpan);//从page_cache的页空间哈希结构中清除
            // delete nextSpan;
            pool_for_spanlist.Delete(nextSpan);
        }
    }

    //向前向后合并完成:插入的PageCache的页空间哈希结构中
    _spanLists[span->_n].PushFront(span);
    span->_isUse = false;//成功插入后,再更改为不使用状态

    //最终合并完成的span的头尾_pageId一定在_idSpanMap结构中,上述前后合并逻辑
}         


